package com.lx.util;


import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

class SignUtil {
    /**加密算法RSA*/
    private static final String KEY_ALGORITHM = "RSA";

    //说明: 签名
    /** @author ylx 2022/6/10 16:45 */
    public static String sign(final Map<String, String> params, String privateStr){
        return encryptByPrivateKey(LX.createLinkString(params), privateStr);
    }
    //说明: 拼接待签名字符串
    /** @author ylx 2022/6/2 16:00 */
    public static String createLinkString(final Map<String, String> params,String exclude, String connStr, boolean encode, boolean skipEmpty) {
        if (LX.isEmpty(params)){
            return "";
        }
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (key.equals(exclude) || (skipEmpty && LX.isEmpty(value))){
                continue;
            }
            if (content.length()>0){
                content.append(connStr);
            }
            content.append(key).append("=").append(encode ? urlEncode(value) : value);
        }
        return content.toString();
    }

    //说明: 对字符串进行编码
    /** @author ylx 2022/6/10 14:52 */
    public static String urlEncode(String src) {
        try {
            return URLEncoder.encode(src, StandardCharsets.UTF_8.name()).replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }
    public static String urlDecode(String src) {
        try {
            return URLDecoder.decode(src, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**编码*/
    public static String base64Encode(byte[] data){
        LX.exObj(data,"需要编码的字符串不能为空!");
        return Base64.getEncoder().encodeToString(data);
    }
    public static byte[] base64EncodeToByteArray(byte[] data){
        LX.exObj(data,"需要编码的字符串不能为空!");
        return Base64.getEncoder().encode(data);
    }
    public static byte[] base64Decode(byte[] data){
        LX.exObj(data,"需要解码的数据不能为空!");
        byte[] decode = Base64.getDecoder().decode(data);
        LX.exObj(decode,"该编码不正确!");
        return decode;
    }
    /** 解码*/
    public static byte[] base64Decode(String str){
        LX.exObj(str,"需要解码的字符串不能为空!");
        str = str.replaceAll("\\s","");
        byte[] decode = Base64.getDecoder().decode(str);
        LX.exObj(decode,"该编码不正确!");
        return decode;
    }

    public static String md5(String string){
        return md5(string.getBytes(StandardCharsets.UTF_8));
    }
    public static String md5(String string, Charset charset){
        return md5(string.getBytes(charset));
    }
    public static String md5(byte[] data){
        try {
            byte[] hash = MessageDigest.getInstance("MD5").digest(data);
            StringBuilder hex = new StringBuilder(hash.length * 2);
            for (byte b : hash) {
                if ((b & 0xFF) < 0x10){
                    hex.append("0");
                }
                hex.append(Integer.toHexString(b & 0xFF));
            }
            return hex.toString();
        }catch (Exception e){
            return LX.exMsg(e);
        }
    }

    //说明:私钥签名
    /** @author ylx 2022/6/10 13:59 */
    public static String encryptByPrivateKey(String data, String privateKey) {
        try {
            return encryptByPrivateKey(data, loadPrivateKey(privateKey));
        } catch (Exception e) {
            return LX.exMsg(e);
        }
    }

    //说明:私钥签名
    /** @author ylx 2022/6/10 13:59 */
    public static String encryptByPrivateKey(String data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256WithRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        byte[] signed = signature.sign();
        return base64Encode(signed);
    }
    //说明: 公钥验证签名
    /** @author ylx 2022/6/10 13:56 */
    public static boolean checkByPublicKey(String data, String sign, String publicKey) {
        try {
            return checkByPublicKey(data, sign, loadPublicKey(publicKey));
        } catch (Exception e) {
            return LX.exMsg(e);
        }
    }
    //说明:公钥验证签名
    /** @author ylx 2022/6/10 13:57 */
    public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256WithRSA");
        signature.initVerify(publicKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        return signature.verify(base64Decode(sign));
    }


    /**
     * 从字符串中加载公钥
     */
    public static PublicKey loadPublicKey(String publicKeyStr) throws Exception {
        try {
            byte[] buffer = base64Decode(publicKeyStr);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            return keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("公钥非法");
        } catch (NullPointerException e) {
            throw new Exception("公钥数据为空");
        }
    }

    /**
     * 从字符串中加载私钥<br>
     */
    public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
        try {
            byte[] buffer = base64Decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            return keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法");
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空");
        }
    }


    /**
     * 密钥算法
     */
    private static final String ALGORITHM = "DES";
    /**
     * 加密/解密算法-工作模式-填充模式
     */
    public static final String CIPHER_ALGORITHM = "DES/CBC/PKCS5Padding";
    /**
     * 生成key
     *
     * @param password
     * @return
     * @throws Exception
     */
    private static Key generateKey(String password,Charset charset) throws Exception {
        DESKeySpec dks = new DESKeySpec(password.getBytes(charset));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
        return keyFactory.generateSecret(dks);
    }

    public static String desEncrypt(String data, String key){
        return base64Encode(desEncrypt(data.getBytes(StandardCharsets.UTF_8),key,key,CIPHER_ALGORITHM,StandardCharsets.UTF_8));
    }
    /**
     * DES加密字符串
     *
     * @param data 待加密字符串
     * @return 加密后内容
     */
    public static byte[] desEncrypt(byte[] data, String key, String ivParameter, String fillingAlgorithm, Charset charset) {
        if (LX.isEmpty(data) || LX.isEmpty(key) || LX.isEmpty(ivParameter)){
            return null;
        }
        try {
            Key secretKey = generateKey(key,charset);
            Cipher cipher = Cipher.getInstance(fillingAlgorithm);
            IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes(charset));
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static String desDecrypt(String data, String key){
        return new String(desDecrypt(base64Decode(data),key,key,CIPHER_ALGORITHM,StandardCharsets.UTF_8),StandardCharsets.UTF_8);
    }
    /**
     * DES解密字符串
     *
     * @param password 解密密码，长度不能够小于8位
     * @param data 待解密字符串
     * @return 解密后内容
     */
    public static byte[] desDecrypt(byte[] data, String key, String ivParameter, String fillingAlgorithm, Charset charset) {
        if (LX.isEmpty(data) || LX.isEmpty(key) || LX.isEmpty(ivParameter)){
            return null;
        }
        try {
            Key secretKey = generateKey(key,charset);
            Cipher cipher = Cipher.getInstance(fillingAlgorithm);
            IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes(charset));
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] parseHexBinary(String s) {
        final int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
        }
        byte[] out = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            int h = hexToBin(s.charAt(i));
            int l = hexToBin(s.charAt(i + 1));
            if (h == -1 || l == -1) {
                throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
            }
            out[i / 2] = (byte) (h * 16 + l);
        }
        return out;
    }
    private static int hexToBin(char ch) {
        if ('0' <= ch && ch <= '9') {
            return ch - '0';
        }
        if ('A' <= ch && ch <= 'F') {
            return ch - 'A' + 10;
        }
        if ('a' <= ch && ch <= 'f') {
            return ch - 'a' + 10;
        }
        return -1;
    }

    private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
    public static String toHexString(byte[] data) {
        StringBuilder r = new StringBuilder(data.length * 2);
        for (byte b : data) {
            r.append(hexCode[(b >> 4) & 0xF]);
            r.append(hexCode[(b & 0xF)]);
        }
        return r.toString();
    }

    public static byte[] hmacSHA(String type, byte[] key,byte[] ... content){
        try {
            Mac mac = Mac.getInstance(type);
            mac.init(new SecretKeySpec(key, type));
            for (byte[] bytes : content) {
                mac.update(bytes);
            }
            return mac.doFinal();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

}
